# gozerbot/fleet.py
#
#

""" fleet is a list of bots. """

__status__ = "seen"

## gozerbot imports

from gozerbot.datadir import datadir
from utils.exception import handle_exception
from utils.generic import waitforqueue
from utils.log import rlog
from utils.locking import lockdec
from threads.thr import start_new_thread, threaded
from config import Config, fleetbotconfigtxt, config
from users import users
from plugins import plugins
from simplejson import load

## basic imports

import Queue
import os
import types
import threading
import time
import pickle
import glob
import logging
import shutil
import thread

## locks

fleetlock = thread.allocate_lock()
fleetlocked = lockdec(fleetlock)

## FleetBotAlreadyExists class

class FleetBotAlreadyExists(Exception):
    pass

## Fleet class

class Fleet(object):

    """
        a fleet contains multiple bots (list of bots). used the datadir
        set in gozerbot/datadir.py

    """

    def __init__(self):
        self.datadir = datadir + os.sep + 'fleet'
        if not os.path.exists(self.datadir): os.mkdir(self.datadir)
        self.startok = threading.Event()
        self.bots = []

    def getfirstbot(self):
        """ return the main bot of the fleet. """
        self.startok.wait()
        return self.bots[0]

    def getfirstjabber(self):
        """ return the first jabber bot of the fleet. """
        self.startok.wait()
        for bot in self.bots:
            if bot.type == 'xmpp': return bot
        
    def size(self):
        """ return number of bots in fleet. """
        return len(self.bots)

    def resume(self, sessionfile):
        """ resume bot from session file. """
        session = load(open(sessionfile))
        for name in session['bots'].keys():
            reto = None
            if name == session['name']: reto = session['channel']
            start_new_thread(self.resumebot, (name, session['bots'][name], reto))
        time.sleep(5)
        self.startok.set()

    def makebot(self, name, cfg=None):
        """ create a bot .. use configuration if provided. """
        if self.byname(name): raise FleetBotAlreadyExists("there is already a %s bot in the fleet" % name)
        rlog(10, 'fleet', 'making bot')
        bot = None
        if not cfg:
            cfg = Config(self.datadir + os.sep + name, 'config', inittxt=fleetbotconfigtxt)
            cfg.save()
        if cfg['type'] == 'irc':
            from gozerbot.irc.bot import Bot
            bot = Bot(name, cfg)
        elif cfg['type'] == 'xmpp' or cfg['type'] == 'jabber':
            from gozerbot.xmpp.bot import Bot
            bot = Bot(name, cfg)
        elif cfg['type'] == 'gozernet':
            from gozerbot.gozernet.bot import GozerNetBot
            bot = GozerNetBot(name, cfg)
        else: rlog(10, 'fleet', '%s .. unproper type: %s' % (cfg['name'], cfg['type']))
        if bot:
            cfg['name'] = bot.name = name
            self.initbot(bot)
            return bot
        raise Exception("can't make %s bot" % name)

    def resumebot(self, botname, data={}, printto=None):
        """ resume individual bot. """
        oldbot = self.byname(botname)
        if oldbot: oldbot.exit()
        cfg = Config(datadir + os.sep + 'fleet' + os.sep + botname, 'config')
        bot = self.makebot(botname, cfg)
        rlog(100, 'fleet', 'bot made: %s' % str(bot))
        if bot:
            if oldbot: self.replace(oldbot, bot)
            else: self.bots.append(bot)
            if not bot.jabber: bot._resume(data, printto)
            else: start_new_thread(bot.connectwithjoin, ())

    def start(self, botlist=[], enable=False, register=False):
        """ start the bots. """
        dirs = []
        got = []
        for bot in botlist: dirs.append(self.datadir + os.sep + bot)
        if not dirs: dirs = glob.glob(self.datadir + os.sep + "*")
        for fleetdir in dirs:
            if fleetdir.endswith('fleet'): continue
            rlog(10, 'fleet', 'found bot: ' + fleetdir)
            cfg = Config(fleetdir, 'config')
            if not cfg: rlog(10, 'fleet', "can't read %s config file" % fleetdir) ; continue
            name = fleetdir.split(os.sep)[-1]
            if not name: rlog(10, 'fleet', "can't read botname from %s config file" % fleetdir) ; continue
            if not enable and not cfg['enable']: rlog(10, 'fleet', '%s bot is disabled' % name) ; continue
            else: rlog(10, 'fleet', '%s bot is enabled' % name)
            if not name in fleetdir: rlog(10, 'fleet', 'bot name in config file doesnt match dir name') ; continue
            try: bot = self.makebot(name, cfg)
            except FleetBotAlreadyExists: rlog(10, 'fleet', 'there is already a fleetbot with the name %s' % name) ; continue
            if bot:
                self.addbot(bot)
                start_new_thread(bot.connectwithjoin, (register,))
                got.append(bot)
        self.startok.set()
        return got

    def save(self):
        """ save fleet data and call save on all the bots. """
        for i in self.bots:
            try: i.save()
            except Exception, ex: handle_exception()

    def avail(self):
        """ show available fleet bots. """
        return os.listdir(self.datadir)

    def list(self):
        """ return list of bot names. """
        result = []
        for i in self.bots: result.append(i.name)
        return result

    def stopall(self):
        """ call stop() on all fleet bots. """
        for i in self.bots:
            try: i.stop()
            except: pass

    def byname(self, name):
        """ return bot by name. """
        for i in self.bots:
            if name == i.name: return i

    def replace(self, name, bot):
        """ replace bot with a new bot. """
        for i in range(len(self.bots)):
            if name == self.bots[i].name: self.bots[i] = bot ; return

    def initbot(self, bot):
        """ initialise a bot. """
        if bot not in self.bots:
            if not os.path.exists(self.datadir + os.sep + bot.name): os.mkdir(self.datadir + os.sep + bot.name)
            if type(bot.cfg['owner']) == types.StringType or type(bot.cfg['owner']) == types.UnicodeType:
                bot.cfg['owner'] = [bot.cfg['owner'], ]
                bot.cfg.save()
            users.make_owner(config['owner'] + bot.cfg['owner'])
            rlog(10, 'fleet', 'added bot: ' + bot.name)

    @fleetlocked
    def addbot(self, bot):
        """
            add a bot to the fleet .. remove all existing bots with the 
            same name.

        """
        if not bot: return False
        for i in range(len(self.bots)-1, -1, -1):
            if self.bots[i].name == bot.name: rlog(10, 'fleet', 'removing %s from fleet' % bot.name) ; del self.bots[i]
        rlog(10, 'fleet', 'adding %s' % bot.name)
        self.bots.append(bot)
        return True

    def connect(self, name):
        """ connect bot to the server. """
        for i in self.bots:
            if i.name == name:
                got = i.connect()
                if got: start_new_thread(i.joinchannels, ()) ; return True
                else: return False

    @fleetlocked
    def delete(self, name):
        """ delete bot with name from fleet. """
        for i in self.bots:
            if i.name == name:
                i.exit()
                self.remove(i)
                i.cfg['enable'] = 0
                i.cfg.save()
                rlog(10, 'fleet', '%s disabled' % i.name)
                return True
        return False

    def remove(self, bot):
        """ delete bot by object. """
        try: self.bots.remove(bot) ; return True
        except ValueError: return False

    def exit(self, name=None, jabber=False):
        """ call exit on all bots. if jabber=True only jabberbots will exit. """
        if not name:
            threads = []
            for i in self.bots:
                if jabber and not i.jabber: pass
                else: threads.append(start_new_thread(i.exit, ()))
            for thr in threads: thr.join()
            return
        for i in self.bots:
            if i.name == name:
                try: i.exit()
                except: handle_exception()
                self.remove(i)
                return True
        return False

    def cmnd(self, event, name, cmnd):
        """ do command on a bot. """
        bot = self.byname(name)
        if not bot: return 0
        from gozerbot.eventbase import EventBase
        j = plugins.clonedevent(bot, event)
        j.onlyqueues = True
        j.txt = cmnd
        q = Queue.Queue()
        j.queues = [q]
        j.speed = 3
        start_new_thread(plugins.trydispatch, (bot, j))
        result = waitforqueue(q)
        if not result: return
        res = ["<%s>" % bot.name, ]
        res += result
        event.reply(res)

    def cmndall(self, event, cmnd):
        """ do a command on all bots. """
        threads = []
        for i in self.bots:
            thread = start_new_thread(self.cmnd, (event, i.name, cmnd))
            threads.append(thread)
        for i in threads: i.join()

    def broadcast(self, txt):
        """ broadcast txt to all bots. """
        for i in self.bots: i.broadcast(txt)

## defines

fleet = Fleet()
